# 评估指标与评分
"""
到目前为止，我们使用精度（正确分类的样本所占的比例）来评估分类性能，使用 R2 来
评估回归性能。但是，总结监督模型在给定数据集上的表现有多种方法，这两个指标只是
其中两种。在实践中，这些评估指标可能不适用于你的应用。在选择模型与调参时，选择
正确的指标是很重要的。
"""
import mglearn
import numpy as np
from matplotlib import pyplot as plt
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV
from sklearn.svm import SVC

# 牢记最终目标
'''
在选择指标时，你应该始终牢记机器学习应用的最终目标。在实践中，我们通常不仅对精
确的预测感兴趣，还希望将这些预测结果用于更大的决策过程。在选择机器学习指标之
前，你应该考虑应用的高级目标，这通常被称为商业指标（business metric）。对于一个机
器学习应用，选择特定算法的结果被称为商业影响（business impact）。4 高级目标可能是避
免交通事故或者减少入院人数，也可能是吸引更多的网站用户或者让用户在你的商店中花
更多的钱。在选择模型或调参时，你应该选择对商业指标具有最大正面影响的模型或参数
值。这通常是很难的，因为要想评估某个模型的商业影响，可能需要将它放在真实的生产
环境中。

在开发的初期阶段调参，仅为了测试就将模型投入生产环境往往是不可行的，因为可能涉
及很高的商业风险或个人风险。想象一下，为了测试无人驾驶汽车的行人避让能力，没有
事先验证就让它直接上路。如果模型很糟糕的话，行人就会遇到麻烦！因此，我们通常需
要找到某种替代的评估程序，使用一种更容易计算的评估指标。例如，我们可以测试对行
人和非行人的图片进行分类并测量精度。请记住，这只是一种替代方法，找到与原始商业
目标最接近的可评估的指标也很有用。应尽可能使用这个最接近的指标来进行模型评估与
选择。评估的结果可能不是一个数字——算法的结果可能是顾客多了 10%，但每位顾客的
花费减少了 15%——但它应该给出选择一个模型而不选另一个所造成的预期商业影响。
'''

# 二分类指标
'''
二分类可能是实践中最常见的机器学习应用，也是概念最简单的应用。但是，即使是评估
这个简单任务也仍有一些注意事项。在深入研究替代指标之前，我们先看一下测量精度可
能会如何误导我们。请记住，对于二分类问题，我们通常会说正类（positive class）和反类
（negative class），而正类是我们要寻找的类。
'''

'''
1. 错误类型
通常来说，精度并不能很好地度量预测性能，因为我们所犯错误的数量并不包含我们感兴
趣的所有信息。想象一个应用，利用自动化测试来筛查癌症的早期发现。如果测试结果为
阴性，那么认为患者是健康的，而如果测试结果为阳性，患者则需要接受额外的筛查。这
里我们将阳性测试结果（表示患有癌症）称为正类，将阴性测试结果称为反类。我们不能
假设模型永远是完美的，它也会犯错。对于任何应用而言，我们都需要问问自己，这些错
误在现实世界中可能有什么后果。
一种可能的错误是健康的患者被诊断为阳性，导致需要进行额外的测试。这给患者带来
了一些费用支出和不便（可能还有精神上的痛苦）。错误的阳性预测叫作假正例（false 
positive）。另一种可能的错误是患病的人被诊断为阴性，因而不会接受进一步的检查和治
疗。未诊断出的癌症可能导致严重的健康问题，甚至可能致命。这种类型的错误（错误的
阴性预测）叫作假反例（false negative）。在统计学中，假正例也叫作第一类错误（type I 
error），假反例也叫作第二类错误（type II error）。我们将坚持使用“假正例”和“假反例”
的说法，因为它们的含义更加明确，也更好记。在癌症诊断的例子中，显然，我们希望尽
量避免假反例，而假正例可以被看作是小麻烦。
虽然这是一个特别极端的例子，但假正例和假反例造成的结果很少相同。在商业应用中，
可以为两种类型的错误分配美元值，即用美元而不是精度来度量某个预测结果的错误。对
于选择使用哪种模型的商业决策而言，这种方法可能更有意义。
'''

'''
2. 不平衡数据集
如果在两个类别中，一个类别的出现次数比另一个多很多，那么错误类型将发挥重要作
用。这在实践中十分常见，一个很好的例子是点击（click-through）预测，其中每个数据点
表示一个“印象”（impression），即向用户展示的一个物项。这个物项可能是广告、相关的
故事，或者是在社交媒体网站上关注的相关人员。目标是预测用户是否会点击看到的某个
特定物项（表示他们感兴趣）。用户对互联网上显示的大多数内容（尤其是广告）都不会
点击。你可能需要向用户展示 100 个广告或文章，他们才会找到足够有趣的内容来点击查
看。这样就会得到一个数据集，其中每 99 个“未点击”的数据点才有 1 个“已点击”的
数据点。换句话说，99% 的样本属于“未点击”类别。这种一个类别比另一个类别出现次
数多很多的数据集，通常叫作不平衡数据集（imbalanced dataset）或者具有不平衡类别的
数据集（dataset with imbalanced classes）。在实际当中，不平衡数据才是常态，而数据中感
兴趣事件的出现次数相同或相似的情况十分罕见。
现在假设你在构建了一个在点击预测任务中精度达到 99% 的分类器。这告诉了你什么？
99% 的精度听起来令人印象深刻，但是它并没有考虑类别不平衡。你不必构建机器学习模
型，始终预测“未点击”就可以得到 99% 的精度。另一方面，即使是不平衡数据，精度达
到 99% 的模型实际上也是相当不错的。但是，精度无法帮助我们区分不变的“未点击”模
型与潜在的优秀模型。
为了便于说明，我们将 digits 数据集中的数字 9 与其他九个类别加以区分，从而创建一个
9:1 的不平衡数据集：
'''

from sklearn.datasets import load_digits, make_blobs

digits = load_digits()
y = digits.target == 9
X_train, X_test, y_train, y_test = train_test_split(
 digits.data, y, random_state=0)

# 我们可以使用 DummyClassifier 来始终预测多数类（这里是“非 9”），以查看精度提供的信
# 息量有多么少：

from sklearn.dummy import DummyClassifier
dummy_majority = DummyClassifier(strategy='most_frequent').fit(X_train, y_train)
pred_most_frequent = dummy_majority.predict(X_test)
print("Unique predicted labels: {}".format(np.unique(pred_most_frequent)))
print("Test score: {:.2f}".format(dummy_majority.score(X_test, y_test)))

'''
我们得到了接近 90% 的精度，却没有学到任何内容。这个结果可能看起来相当好，但请
思考一会儿。想象一下，有人告诉你他们的模型精度达到 90%。你可能会认为他们做得很
好。但根据具体问题，也可能是仅预测了一个类别！我们将这个结果与使用一个真实分类
器的结果进行对比：
'''
from sklearn.tree import DecisionTreeClassifier
tree = DecisionTreeClassifier(max_depth=2).fit(X_train, y_train)
pred_tree = tree.predict(X_test)
print("Test score: {:.2f}".format(tree.score(X_test, y_test)))

'''
从精度来看，DecisionTreeClassifier 仅比常数预测稍好一点。这可能表示我们使用
DecisionTreeClassifier 的方法有误，也可能是因为精度实际上在这里不是一个很好的度量。
为了便于对比，我们再评估两个分类器，LogisticRegression 与默认的 DummyClassifier，
其中后者进行随机预测，但预测类别的比例与训练集中的比例相同 
'''
from sklearn.linear_model import LogisticRegression
dummy = DummyClassifier().fit(X_train, y_train)
pred_dummy = dummy.predict(X_test)
print("dummy score: {:.2f}".format(dummy.score(X_test, y_test)))

logreg = LogisticRegression(C=0.1).fit(X_train, y_train)
pred_logreg = logreg.predict(X_test)
print("logreg score: {:.2f}".format(logreg.score(X_test, y_test)))

'''
显而易见，产生随机输出的虚拟分类器是所有分类器中最差的（精度最低），而
LogisticRegression 则给出了非常好的结果。但是，即使是随机分类器也得到了超过 80% 的
精度。这样很难判断哪些结果是真正有帮助的。这里的问题在于，要想对这种不平衡数据的
预测性能进行量化，精度并不是一种合适的度量。在本章接下来的内容中，我们将探索在选
择模型方面能够提供更好指导的其他指标。我们特别希望有一个指标可以告诉我们，一个模
型比“最常见”预测（由 pred_most_frequent 给出）或随机预测（由 pred_dummy 给出）要好
多少。如果我们用一个指标来评估模型，那么这个指标应该能够淘汰这些无意义的预测。
'''


'''
3. 混淆矩阵
对于二分类问题的评估结果，一种最全面的表示方法是使用混淆矩阵（confusion matrix）。
我们利用 confusion_matrix 函数来检查上一节中 LogisticRegression 的预测结果。我们已
经将测试集上的预测结果保存在 pred_logreg 中：
'''

from sklearn.metrics import confusion_matrix, precision_recall_curve, average_precision_score, roc_curve, roc_auc_score

confusion = confusion_matrix(y_test, pred_logreg)
print("Confusion matrix:\n{}".format(confusion))

'''
confusion_matrix 的输出是一个 2×2 数组，其中行对应于真实的类别，列对应于预测的类
别。数组中每个元素给出属于该行对应类别（这里是“非 9”和“9”）的样本被分类到该
列对应类别中的数量。
'''

mglearn.plots.plot_confusion_matrix_illustration()

'''
混淆矩阵主对角线 6 上的元素对应于正确的分类，而其他元素则告诉我们一个类别中有多少
样本被错误地划分到其他类别中

如果我们将“9”作为正类，那么就可以将混淆矩阵的元素与前面介绍过的假正例（false 
positive）和假反例（false negative）两个术语联系起来。为了使图像更加完整，我们将
正类中正确分类的样本称为真正例（true positive），将反类中正确分类的样本称为真反例
（true negative）。这些术语通常缩写为 FP、FN、TP 和 TN，这样就可以得到下图对混淆矩
阵的解释
'''

mglearn.plots.plot_binary_confusion_matrix()

# 下面我们用混淆矩阵来比较前面拟合过的模型（两个虚拟模型、决策树和 Logistic 回归）：
print("Most frequent class:")
print(confusion_matrix(y_test, pred_most_frequent))
print("\nDummy model:")
print(confusion_matrix(y_test, pred_dummy))
print("\nDecision tree:")
print(confusion_matrix(y_test, pred_tree))
print("\nLogistic Regression")
print(confusion_matrix(y_test, pred_logreg))

'''
观察混淆矩阵，很明显可以看出 pred_most_frequent 有问题，因为它总是预测同一个类
别。另一方面，pred_dummy 的真正例数量很少（4 个），特别是与假反例和假正例的数量相
比——假正例的数量竟然比真正例还多！决策树的预测比虚拟预测更有意义，即使二者精
度几乎相同。最后，我们可以看到，Logistic 回归在各方面都比 pred_tree 要好：它的真正
例和真反例的数量更多，而假正例和假反例的数量更少。从这个对比中可以明确看出，只
有决策树和 Logistic 回归给出了合理的结果，并且 Logistic 回归的效果全面好于决策树。
但是，检查整个混淆矩阵有点麻烦，虽然我们通过观察矩阵的各个方面得到了很多深入见
解，但是这个过程是人工完成的，也是非常定性的。有几种方法可以总结混淆矩阵中包含
的信息，我们将在后面进行讨论。
'''

'''
与精度的关系。我们已经讲过一种总结混淆矩阵结果的方法——计算精度，其公式表达如
下所示
Accuracy = (TP+TN)/(TP+TN+FP+FN)
换句话说，精度是正确预测的数量（TP 和 TN）除以所有样本的数量（混淆矩阵中所有元
素的总和）。

准确率、召回率与 f-分数。总结混淆矩阵还有几种方法，其中最常见的就是准确率和召回
率。准确率（precision）度量的是被预测为正例的样本中有多少是真正的正例：
Precision = TP/(TP+FP)

如果目标是限制假正例的数量，那么可以使用准确率作为性能指标。举个例子，想象一个
模型，它预测一种新药在临床试验治疗中是否有效。众所周知，临床试验非常昂贵，制
药公司只有在非常确定药物有效的情况下才会进行试验。因此，模型不会产生很多假正
例是很重要的——换句话说，模型的准确率很高。准确率也被称为阳性预测值（positive 
predictive value，PPV）。

另一方面，召回率（recall）度量的是正类样本中有多少被预测为正类：
Recall = TP/(TP+FN)
在优化召回率与优化准确率之间需要折中。如果你预测所有样本都属于正类，那么可以轻
松得到完美的召回率——没有假反例，也没有真反例。但是，将所有样本都预测为正类，
将会得到许多假正例，因此准确率会很低。与之相反，如果你的模型只将一个最确定的数
据点预测为正类，其他点都预测为反类，那么准确率将会很完美（假设这个数据点实际上
就属于正类），但是召回率会非常差。

在机器学习社区中，准确率和召回率是最常用的二分类度量，但其他社区可能使用其他相关指标。
'''

'''
虽然准确率和召回率是非常重要的度量，但是仅查看二者之一无法为你提供完整的图景。
将两种度量进行汇总的一种方法是 f-分数（f-score）或 f-度量（f-measure），它是准确率与
召回率的调和平均：
F = 2*(precision*recall)/(precision+recall)

这一特定变体也被称为 f1-分数（f1-score）。由于同时考虑了准确率和召回率，所以它对于
不平衡的二分类数据集来说是一种比精度更好的度量。我们对之前计算过的“9 与其余”
数据集的预测结果计算 f1- 分数。这里我们假定“9”类是正类（标记为 True，其他样本被
标记为 False），因此正类是少数类：
'''
from sklearn.metrics import f1_score
print("f1 score most frequent: {:.2f}".format(
 f1_score(y_test, pred_most_frequent)))
print("f1 score dummy: {:.2f}".format(f1_score(y_test, pred_dummy)))
print("f1 score tree: {:.2f}".format(f1_score(y_test, pred_tree)))
print("f1 score logistic regression: {:.2f}".format(
 f1_score(y_test, pred_logreg)))

# f1 score most frequent: 0.00
# f1 score dummy: 0.00
# f1 score tree: 0.55
# f1 score logistic regression: 0.92

'''
这里我们可以注意到两件事情。第一，我们从 most_frequent 的预测中得到一条错误信息，
因为预测的正类数量为 0（使得 f- 分数的分母为 0）。第二，我们可以看到虚拟预测与决策
树预测之间有很大的区别，而仅观察精度时二者的区别并不明显。利用 f- 分数进行评估，
我们再次用一个数字总结了预测性能。但是，f- 分数似乎比精度更加符合我们对好模型的
直觉。然而，f- 分数的一个缺点是比精度更加难以解释。
'''

# 如果我们想要对准确率、召回率和 f1- 分数做一个更全面的总结，可以使用 classifi
# cation_report 这个很方便的函数，它可以同时计算这三个值，并以美观的格式打印出来：
from sklearn.metrics import classification_report
print(classification_report(y_test, pred_most_frequent,
 target_names=["not nine", "nine"]))

'''
classification_report 函数为每个类别（这里是 True 和 False）生成一行，并给出以该
类别作为正类的准确率、召回率和 f- 分数。前面我们假设较少的“9”类是正类。如果
将正类改为“not nine”（非 9），我们可以从 classification_report 的输出中看出，利用
most_frequent 模型得到的 f-分数为 0.94。此外，对于“not nine”类别，召回率是 1，因
为我们将所有样本都分类为“not nine”。f- 分数旁边的最后一列给出了每个类别的支持
（support），它表示的是在这个类别中真实样本的数量。
分类报告的最后一行显示的是对应指标的加权平均（按每个类别中的样本个数加权）。下
面还有两个报告，一个是虚拟分类器的，一个是 Logistic 回归的：
'''

print(classification_report(y_test, pred_dummy,target_names=["not nine", "nine"]))

print(classification_report(y_test, pred_logreg,
 target_names=["not nine", "nine"]))

'''
在查看报告时你可能注意到了，虚拟模型与好模型之间的区别不再那么明显。选择哪个
类作为正类对指标有很大影响。虽然在以“nine”类作为正类时虚拟分类的 f- 分数是 0.10
（对比 Logistic 回归的 0.89），而以“not nine”类作为正类时二者的 f- 分数分别是 0.91 和
0.99，两个结果看起来都很合理。不过同时查看所有数字可以给出非常准确的图像，我们
可以清楚地看到 Logistic 回归模型的优势。
'''

'''
4. 考虑不确定性
混淆矩阵和分类报告为一组特定的预测提供了非常详细的分析。但是，预测本身已经丢弃
了模型中包含的大量信息。正如我们在第 2 章中所讨论的那样，大多数分类器都提供了一
个 decision_function 或 predict_proba 方法来评估预测的不确定度。预测可以被看作是以
某个固定点作为 decision_function 或 predict_proba 输出的阈值——在二分类问题中，我
们使用 0 作为决策函数的阈值，0.5 作为 predict_proba 的阈值。

下面是一个不平衡二分类任务的示例，反类中有 400 个点，而正类中只有 50 个点。训练
数据如图 5-12 左侧所示。我们在这个数据上训练一个核 SVM 模型，训练数据右侧的图像
将决策函数值绘制为热图。你可以在图像偏上的位置看到一个黑色圆圈，表示 decision_
function 的阈值刚好为 0。在这个圆圈内的点将被划为正类，圆圈外的点将被划为反类：
'''
X, y = make_blobs(n_samples=400, centers=2, cluster_std=[7.0, 2], random_state=22)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
svc = SVC(gamma=.05).fit(X_train, y_train)
mglearn.plots.plot_decision_threshold()
plt.show()

# 我们可以使用 classification_report 函数来评估两个类别的准确率与召回率：
print(classification_report(y_test, svc.predict(X_test)))


'''
对于类别 1，我们得到了一个相当低的准确率，而召回率则令人糊涂（mixed）。由于类别
0 要大得多，所以分类器将重点放在将类别 0 分类正确，而不是较小的类别 1。
假设在我们的应用中，类别 1 具有高召回率更加重要，正如前面的癌症筛查例子那样。这
意味着我们愿意冒险有更多的假正例（假的类别 1），以换取更多的真正例（可增大召回
率）。svc.predict 生成的预测无法满足这个要求，但我们可以通过改变决策阈值不等于 0
来将预测重点放在使类别 1 的召回率更高。默认情况下，decision_function 值大于 0 的点
将被划为类别 1。我们希望将更多的点划为类别 1，所以需要减小阈值：
'''

y_pred_lower_threshold = svc.decision_function(X_test) > -.8
print(classification_report(y_test, y_pred_lower_threshold))

'''
正如所料，类别 1 的召回率增大，准确率减小。现在我们将更大的空间区域划为类别 1，
正如图 5-12 右上图中所示。如果你认为准确率比召回率更重要，或者反过来，或者你的数
据严重不平衡，那么改变决策阈值是得到更好结果的最简单方法。由于 decision_function
的取值可能在任意范围，所以很难提供关于如何选取阈值的经验法则。
'''

'''
如果你设置了阈值，那么要小心不要在测试集上这么做。与其他任何参数一样，
在测试集上设置决策阈值可能会得到过于乐观的结果。可以使用验证集或交叉验证来代替。
'''

'''
对于实现了 predict_proba 方法的模型来说，选择阈值可能更简单，因为 predict_proba 的输
出固定在 0 到 1 的范围内，表示的是概率。默认情况下，0.5 的阈值表示，如果模型以超过
50% 的概率“确信”一个点属于正类，那么就将其划为正类。增大这个阈值意味着模型需要
更加确信才能做出正类的判断（较低程度的确信就可以做出反类的判断）。虽然使用概率可
能比使用任意阈值更加直观，但并非所有模型都提供了不确定性的实际模型（一棵生长到最
大深度的 DecisionTree 总是 100% 确信其判断，即使很可能是错的）。这与校准（calibration）
的概念相关：校准模型是指能够为其不确定性提供精确度量的模型。
'''

'''
5. 准确率-召回率曲线
如前所述，改变模型中用于做出分类决策的阈值，是一种调节给定分类器的准确率和召回
率之间折中的方法。你可能希望仅遗漏不到 10% 的正类样本，即希望召回率能达到 90%。
这一决策取决于应用，应该是由商业目标驱动的。一旦设定了一个具体目标（比如对某一
类别的特定召回率或准确率），就可以适当地设定一个阈值。总是可以设置一个阈值来满
足特定的目标，比如 90% 的召回率。难点在于开发一个模型，在满足这个阈值的同时仍具
有合理的准确率——如果你将所有样本都划为正类，那么将会得到 100% 的召回率，但你
的模型毫无用处。
对分类器设置要求（比如 90% 的召回率）通常被称为设置工作点（operating point）。在业
务中固定工作点通常有助于为客户或组织内的其他小组提供性能保证。
在开发新模型时，通常并不完全清楚工作点在哪里。因此，为了更好地理解建模问题，很
有启发性的做法是，同时查看所有可能的阈值或准确率和召回率的所有可能折中。利用
一种叫作准确率 - 召回率曲线（precision-recall curve）的工具可以做到这一点。你可以在
sklearn.metrics 模块中找到计算准确率 - 召回率曲线的函数。这个函数需要真实标签与预
测的不确定度，后者由 decision_function 或 predict_proba 给出：
'''

precision, recall, thresholds = precision_recall_curve(
 y_test, svc.decision_function(X_test))

'''
precision_recall_curve 函数返回一个列表，包含按顺序排序的所有可能阈值（在决策
函数中出现的所有值）对应的准确率和召回率，这样我们就可以绘制一条曲线
'''
# 使用更多数据点来得到更加平滑的曲线
X, y = make_blobs(n_samples=4000, centers=2, cluster_std=[7.0, 2],
 random_state=22)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
svc = SVC(gamma=.05).fit(X_train, y_train)
precision, recall, thresholds = precision_recall_curve(
 y_test, svc.decision_function(X_test))
# 找到最接近于0的阈值
close_zero = np.argmin(np.abs(thresholds))
plt.plot(precision[close_zero], recall[close_zero], 'o', markersize=10,
 label="threshold zero", fillstyle="none", c='k', mew=2)
plt.plot(precision, recall, label="precision recall curve")
plt.xlabel("Precision")
plt.ylabel("Recall")
# plt.show()

'''
曲线越靠近右上角，则分类器越好。右上角的点表示对于同一个阈值，准确率和召回率都
很高。曲线从左上角开始，这里对应于非常低的阈值，将所有样本都划为正类。提高阈值
可以让曲线向准确率更高的方向移动，但同时召回率降低。继续增大阈值，大多数被划为
正类的点都是真正例，此时准确率很高，但召回率更低。随着准确率的升高，模型越能够
保持较高的召回率，则模型越好。

进一步观察这条曲线，可以发现，利用这个模型可以得到约 0.5 的准确率，同时保持很高
的召回率。如果我们想要更高的准确率，那么就必须牺牲很多召回率。换句话说，曲线左
侧相对平坦，说明在准确率提高的同时召回率没有下降很多。当准确率大于 0.5 之后，准
确率每增加一点都会导致召回率下降许多。
'''

'''
不同的分类器可能在曲线上不同的位置（即在不同的工作点）表现很好。我们来比较
一下在同一数据集上训练的 SVM 与随机森林。RandomForestClassifier 没有 decision_
function，只有 predict_proba。precision_recall_curve 函数的第二个参数应该是正类
（类别 1）的确定性度量，所以我们传入样本属于类别 1 的概率（即 rf.predict_proba(X_
test)[:, 1]）。二分类问题的 predict_proba 的默认阈值是 0.5，所以我们在曲线上标出这
个点
'''

rf = RandomForestClassifier(n_estimators=100, random_state=0,max_features=2)
rf.fit(X_train, y_train)

precision_rf,recall_rf,thresholds_rf = precision_recall_curve(y_test,rf.predict_proba(X_test)[:,1])
plt.plot(precision,recall,label="svc")
plt.plot(precision[close_zero],recall[close_zero],'o',markersize=10,label="threshold zero svc",fillstyle="none",c="k",mew=2)
plt.plot(precision_rf, recall_rf, label="rf")
close_default_rf = np.argmin(np.abs(thresholds_rf - 0.5))
plt.plot(precision_rf[close_default_rf], recall_rf[close_default_rf], '^', c='k',
 markersize=10, label="threshold 0.5 rf", fillstyle="none", mew=2)
plt.xlabel("Precision")
plt.ylabel("Recall")
plt.legend(loc="best")
# plt.show()

'''
从这张对比图中可以看出，随机森林在极值处（要求很高的召回率或很高的准确率）的表
现更好。在中间位置（准确率约为 0.7）SVM 的表现更好。如果我们只查看 f1- 分数来比较
二者的总体性能，那么可能会遗漏这些细节。f1- 分数只反映了准确率 - 召回率曲线上的一
个点，即默认阈值对应的那个点：
'''
print("f1_score of random forest: {:.3f}".format(
 f1_score(y_test, rf.predict(X_test))))
print("f1_score of svc: {:.3f}".format(f1_score(y_test, svc.predict(X_test))))

# f1_score of random forest: 0.920
# f1_score of svc: 0.921

'''
比较这两条准确率 - 召回率曲线，可以为我们提供大量详细的洞见，但这是一个相当麻烦的
过程。对于自动化模型对比，我们可能希望总结曲线中包含的信息，而不限于某个特定的阈
值或工作点。总结准确率 - 召回率曲线的一种方法是计算该曲线下的积分或面积，也叫作
平均准确率（average precision）。8 你可以使用 average_precision_score 函数来计算平均准确
率。因为我们要计算准确率 - 召回率曲线并考虑多个阈值，所以需要向 average_precision_
score 传入 decision_function 或 predict_proba 的结果，而不是 predict 的结果：
'''

ap_rf = average_precision_score(y_test, rf.predict(X_test))
ap_svc = average_precision_score(y_test,svc.decision_function(X_test))
print("Average precision of random forest: {:.3f}".format(ap_rf))
print("Average precision of svc: {:.3f}".format(ap_svc))
# Average precision of random forest: 0.868
# Average precision of svc: 0.914

'''
在对所有可能的阈值进行平均时，我们看到随机森林和 SVC 的表现差不多好，随机森林稍
稍领先。这与前面从 f1_score 中得到的结果大为不同。因为平均准确率是从 0 到 1 的曲线
下的面积，所以平均准确率总是返回一个在 0（最差）到 1（最好）之间的值。随机分配
decision_function 的分类器的平均准确率是数据集中正例样本所占的比例。
'''

# 受试者工作特征（ROC）与AUC
'''
还有一种常用的工具可以分析不同阈值的分类器行为：受试者工作特征曲线（receiver 
operating characteristics curve），简称为 ROC 曲线（ROC curve）。与准确率 - 召回率曲
线类似，ROC 曲线考虑了给定分类器的所有可能的阈值，但它显示的是假正例率（false 
positive rate，FPR）和真正例率（true positive rate，TPR），而不是报告准确率和召回率。
回想一下，真正例率只是召回率的另一个名称，而假正例率则是假正例占所有反类样本的
比例：

FPR = FP/(FP+TN)
可以用 roc_curve 函数来计算 ROC 曲线
'''

fpr,tpr,thresholds=roc_curve(y_test,svc.decision_function(X_test))
plt.plot(fpr,tpr,label="ROC Curve")
plt.xlabel("FPR")
plt.ylabel("TPR(recall)")
close_zero = np.argmin(np.abs(thresholds))
plt.plot(fpr[close_zero],tpr[close_zero],'o',markersize=10,label="threshold zero",fillstyle="none",c="k",mew=2)
plt.legend(loc=4)
plt.show()

'''
对于 ROC 曲线，理想的曲线要靠近左上角：你希望分类器的召回率很高，同时保持假正
例率很低。从曲线中可以看出，与默认阈值 0 相比，我们可以得到明显更高的召回率（约
0.9），而 FPR 仅稍有增加。最接近左上角的点可能是比默认选择更好的工作点。同样请注
意，不应该在测试集上选择阈值，而是应该在单独的验证集上选择。
'''

'''
与准确率 - 召回率曲线一样，我们通常希望使用一个数字来总结 ROC 曲线，即曲线下的
面积［通常被称为 AUC（area under the curve），这里的曲线指的就是 ROC 曲线］。我们可
以利用 roc_auc_score 函数来计算 ROC 曲线下的面积：
'''
rf_auc = roc_auc_score(y_test,rf.predict_proba(X_test)[:,1])
svc_auc = roc_auc_score(y_test,svc.decision_function(X_test))
print("AUC for Random Forest: {:.3f}".format(rf_auc))
print("AUC for SVC: {:.3f}".format(svc_auc))
# AUC for Random Forest: 0.954
# AUC for SVC: 0.953

'''
利用 AUC 分数来比较随机森林和 SVM，我们发现随机森林的表现比 SVM 要略好一些。
回想一下，由于平均准确率是从 0 到 1 的曲线下的面积，所以平均准确率总是返回一个 0
（最差）到 1（最好）之间的值。随机预测得到的 AUC 总是等于 0.5，无论数据集中的类别
多么不平衡。对于不平衡的分类问题来说，AUC 是一个比精度好得多的指标。AUC 可以
被解释为评估正例样本的排名（ranking）。它等价于从正类样本中随机挑选一个点，由分
类器给出的分数比从反类样本中随机挑选一个点的分数更高的概率。因此，AUC 最高为
1，这说明所有正类点的分数高于所有反类点。对于不平衡类别的分类问题，使用 AUC 进
行模型选择通常比使用精度更有意义。
'''

'''
我们回到前面研究过的例子：将 digits 数据集中的所有 9 与所有其他数据加以区分。我
们将使用 SVM 对数据集进行分类，分别使用三种不同的内核宽度（gamma）设置
'''
y=digits.target == 9
X_train, X_test, y_train, y_test = train_test_split(
 digits.data, y, random_state=0)
plt.figure()
for gamma in [1, 0.05, 0.01]:
 svc = SVC(gamma=gamma).fit(X_train, y_train)
 accuracy = svc.score(X_test, y_test)
 auc = roc_auc_score(y_test, svc.decision_function(X_test))
 fpr, tpr, _ = roc_curve(y_test , svc.decision_function(X_test))
 print("gamma = {:.2f} accuracy = {:.2f} AUC = {:.2f}".format(
 gamma, accuracy, auc))
 plt.plot(fpr, tpr, label="gamma={:.3f}".format(gamma))
plt.xlabel("FPR")
plt.ylabel("TPR")
plt.xlim(-0.01, 1)
plt.ylim(0, 1.02)
plt.legend(loc="best")

'''
对于三种不同的 gamma 设置，其精度是相同的，都等于 90%。这可能与随机选择的性能相
同，也可能不同。但是观察 AUC 以及对应的曲线，我们可以看到三个模型之间有明显的
区别。对于 gamma=1.0，AUC 实际上处于随机水平，即 decision_function 的输出与随机
结果一样好。对于 gamma=0.05，性能大幅提升至 AUC 等于 0.9。最后，对于 gamma=0.01，

我们得到等于 1.0 的完美 AUC。这意味着根据决策函数，所有正类点的排名要高于所有反
类点。换句话说，利用正确的阈值，这个模型可以对所有数据进行完美分类！ 10 知道这一
点，我们可以调节这个模型的阈值并得到很好的预测结果。如果我们仅使用精度，那么将
永远不会发现这一点。
因此，我们强烈建议在不平衡数据上评估模型时使用 AUC。但请记住，AUC 没有使用默
认阈值，因此，为了从高 AUC 的模型中得到有用的分类结果，可能还需要调节决策阈值。
'''

# 多分类指标
'''
前面我们已经深入讨论了二分类任务的评估，下面来看一下对多分类问题的评估指标。多
分类问题的所有指标基本上都来自于二分类指标，但是要对所有类别进行平均。多分类的
精度被定义为正确分类的样本所占的比例。同样，如果类别是不平衡的，精度并不是很
好的评估度量。想象一个三分类问题，其中 85% 的数据点属于类别 A，10% 属于类别 B，
5% 属于类别 C。在这个数据集上 85% 的精度说明了什么？一般来说，多分类结果比二分
类结果更加难以理解。除了精度，常用的工具有混淆矩阵和分类报告，我们在上一节二分
类的例子中都见过。下面我们将这两种详细的评估方法应用于对 digits 数据集中 10 种不
同的手写数字进行分类的任务：
'''

from sklearn.metrics import accuracy_score
X_train, X_test, y_train, y_test = train_test_split(
 digits.data, digits.target, random_state=0)
lr = LogisticRegression().fit(X_train, y_train)
pred = lr.predict(X_test)
print("Accuracy: {:.3f}".format(accuracy_score(y_test, pred)))
print("Confusion matrix:\n{}".format(confusion_matrix(y_test, pred)))

'''
模型的精度为 95.3%，这表示我们已经做得相当好了。混淆矩阵为我们提供了更多细节。
与二分类的情况相同，每一行对应于真实标签，每一列对应于预测标签。
'''

'''
对于第一个类别（数字 0），它包含 37 个样本，所有这些样本都被划为类别 0（即类别 0
没有假反例）。我们之所以可以看出这一点，是因为混淆矩阵第一行中其他所有元素都为
0。我们还可以看到，没有其他数字被误分类为类别 0，这是因为混淆矩阵第一列中其他所
有元素都为 0（即类别 0 没有假正例）。但是有些数字与其他数字混在一起——比如数字 2
（第 3 行），其中有 3 个被划分到数字 3 中（第 4 列）。还有一个数字 3 被划分到数字 2 中
（第 4 行第 3 列），一个数字 8 被划分到数字 2 中（第 9 行第 3 列）。
'''

# 利用 classification_report 函数，我们可以计算每个类别的准确率、召回率和 f- 分数：
print(classification_report(y_test, pred))

'''
不出所料，类别 0 的准确率和召回率都是完美的 1，因为这个类别中没有混淆。另一方面，
对于类别 7，准确率为 1，这是因为没有其他类别被误分类为 7；而类别 6 没有假反例，所
以召回率等于 1。我们还可以看到，模型对类别 8 和类别 3 的表现特别不好。
'''

'''
对于多分类问题中的不平衡数据集，最常用的指标就是多分类版本的 f- 分数。多分类 f- 分
数背后的想法是，对每个类别计算一个二分类 f- 分数，其中该类别是正类，其他所有类别
组成反类。然后，使用以下策略之一对这些按类别 f- 分数进行平均。
• “宏”（macro）平均：计算未加权的按类别 f- 分数。它对所有类别给出相同的权重，无
论类别中的样本量大小。
• “加权”（weighted）平均：以每个类别的支持作为权重来计算按类别 f- 分数的平均值。
分类报告中给出的就是这个值。
• “微”（micro）平均：计算所有类别中假正例、假反例和真正例的总数，然后利用这些
计数来计算准确率、召回率和 f- 分数。
如果你对每个样本等同看待，那么推荐使用“微”平均 f1- 分数；如果你对每个类别等同看
待，那么推荐使用“宏”平均 f1- 分数：
'''

print("Micro average f1 score: {:.3f}".format
 (f1_score(y_test, pred, average="micro")))
print("Macro average f1 score: {:.3f}".format
 (f1_score(y_test, pred, average="macro")))

# 回归指标
'''
对回归问题可以像分类问题一样进行详细评估，例如，对目标值估计过高与目标值估计过
低进行对比分析。但是，对于我们见过的大多数应用来说，使用默认 R2 就足够了，它由
所有回归器的 score 方法给出。业务决策有时是根据均方误差或平均绝对误差做出的，这
可能会鼓励人们使用这些指标来调节模型。但是一般来说，我们认为 R2 是评估回归模型
的更直观的指标。
'''

'''
在模型选择中使用评估指标
前面详细讨论了许多种评估方法，以及如何根据真实情况和具体模型来应用这些方法。但
我们通常希望，在使用 GridSearchCV 或 cross_val_score 进行模型选择时能够使用 AUC
等指标。幸运的是，scikit-learn 提供了一种非常简单的实现方法，就是 scoring 参数，
它可以同时用于 GridSearchCV 和 cross_val_score。你只需提供一个字符串，用于描述想
要使用的评估指标。举个例子，我们想用 AUC 分数对 digits 数据集中“9 与其他”任务
上的 SVM 分类器进行评估。想要将分数从默认值（精度）修改为 AUC，可以提供 "roc_
auc" 作为 scoring 参数的值：
'''
# 分类问题的默认评分是精度
print("Default scoring: {}".format(
 cross_val_score(SVC(), digits.data, digits.target == 9)))
# 指定"scoring="accuracy"不会改变结果
explicit_accuracy = cross_val_score(SVC(), digits.data, digits.target == 9,
 scoring="accuracy")
print("Explicit accuracy scoring: {}".format(explicit_accuracy))
roc_auc = cross_val_score(SVC(), digits.data, digits.target == 9,
 scoring="roc_auc")
print("AUC scoring: {}".format(roc_auc))

# 类似地，我们可以改变 GridSearchCV 中用于选择最佳参数的指标：
X_train, X_test, y_train, y_test = train_test_split(
 digits.data, digits.target == 9, random_state=0)
# 我们给出了不太好的网格来说明：
param_grid = {'gamma': [0.0001, 0.01, 0.1, 1, 10]}
# 使用默认的精度：
grid = GridSearchCV(SVC(), param_grid=param_grid)
grid.fit(X_train, y_train)
print("Grid-Search with accuracy")
print("Best parameters:", grid.best_params_)
print("Best cross-validation score (accuracy)): {:.3f}".format(grid.best_score_))
print("Test set AUC: {:.3f}".format(
 roc_auc_score(y_test, grid.decision_function(X_test))))
print("Test set accuracy: {:.3f}".format(grid.score(X_test, y_test)))

# 使用AUC评分来代替：
grid = GridSearchCV(SVC(), param_grid=param_grid, scoring="roc_auc")
grid.fit(X_train, y_train)
print("\nGrid-Search with AUC")
print("Best parameters:", grid.best_params_)
print("Best cross-validation score (AUC): {:.3f}".format(grid.best_score_))
print("Test set AUC: {:.3f}".format(
 roc_auc_score(y_test, grid.decision_function(X_test))))
print("Test set accuracy: {:.3f}".format(grid.score(X_test, y_test)))

'''
在使用精度时，选择的参数是 gamma=0.0001，而使用 AUC 时选择的参数是 gamma=0.01。
在两种情况下，交叉验证精度与测试集精度是一致的。但是，使用 AUC 找到的参数设置，
对应的 AUC 更高，甚至对应的精度也更高。11
对于分类问题，scoring 参数最重要的取值包括：accuracy（默认值）、roc_auc（ROC 曲
线下方的面积）、average_precision（准确率 - 召回率曲线下方的面积）、f1、f1_macro、
f1_micro 和 f1_weighted（这四个是二分类的 f1- 分数以及各种加权变体）。对于回归问题，
最常用的取值包括：r2（R2 分数）、mean_squared_error（均方误差）和 mean_absolute_
error（平均绝对误差）。你可以在文档中找到所支持参数的完整列表（
'''

# from sklearn.metrics.scorer import SCORERS
# print("Available scorers:\n{}".format(sorted(SCORERS.keys())))

